
/* GCSx
** FOLDER.CPP
**
** Folders within world browser (user-definable)
*/

/*****************************************************************************
** Copyright (C) 2003-2006 Janson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
*****************************************************************************/

#include "all.h"

FolderEdit::FolderEdit(WorldEdit* myWorld, int myId) : items() { start_func
    assert(myWorld);
    
    id = myId;
    cached = 0;
    lockCount = 0;
    cacheFile = NULL;
    world = myWorld;
    
    sorted = 1;
    defaultFont = 0;
    defaultTileSet = 0;
    defaultScene = 0;
    defaultAnimGroup = 0;
    defaultScript = 0;
    defaultLibrary = 0;
    icon = 0;

    // Use an "Untitled" name
    if (myId) {
        name = "New Folder";
        headerModified = contentModified = 1;
    }
    else {
        name = blankString;
        headerModified = contentModified = 0;
    }

    browserNode = NULL;
    disassociated = 0;
    desktop->broadcastObjChange(OBJ_FOLDER | OBJMOD_CREATE, this, 0, 0);
}

FolderEdit::~FolderEdit() { start_func
    delete cacheFile;

    // @TODO: doesn't appear to be needed; no way to delete accidentally
    // if (!disassociated) desktop->broadcastObjChange(OBJ_FOLDER | OBJMOD_DELETE, this, 0, 0);

    // World should be the one deleting us, which will delete our node also
}
    
void FolderEdit::cacheLoad() throw_File { start_func
    if (cached) {
        // load into memory
        assert(cacheFile);
        assert(cacheFile->getVersion() <= 1);
        
        /*
        cacheFile->rewind();
        
        try {
            // (this is where we would load content, if folders had any)
        }
        catch (...) {
            // Recache ourselves
            throw;
        }
        */
        
        // We retain this pointer and reuse it IF we
        // unlock to 0 AND aren't modified
        cached = 0;
    }
}

void FolderEdit::loadHeader(FileRead* file) throw_File { start_func
    assert(items.empty());

    if (file->getVersion() > 1) {
        throw FileException("Unsupported folder version %d", file->getVersion());
    }

    file->readStr(name);
    id = file->readInt();
    sorted = file->readInt();
    defaultFont = file->readInt();
    defaultTileSet = file->readInt();
    defaultScene = file->readInt();
    defaultAnimGroup = file->readInt();
    defaultScript = file->readInt();
    defaultLibrary = file->readInt();
    icon = file->readInt();    
    int itemCount = file->readInt();
    
    // Check validity (1)
    if ((!id) || (itemCount < 0) || (icon < 0) || (icon > 36) ||
        ((sorted != 0) && (sorted != 1)) ||
        ((defaultFont != 0) && (defaultFont != 1)) ||
        ((defaultTileSet != 0) && (defaultTileSet != 1)) ||
        ((defaultScene != 0) && (defaultScene != 1)) ||
        ((defaultAnimGroup != 0) && (defaultAnimGroup != 1)) ||
        ((defaultScript != 0) && (defaultScript != 1)) ||
        ((defaultLibrary != 0) && (defaultLibrary != 1))) {
        throw FileException("Corrupted folder header");
    }
    
    // Read items
    for (; itemCount > 0; --itemCount) {
        FolderItem work;

        work.itemBlockType = file->readInt();
        work.itemId = file->readInt();
        work.item = getWorldEdit()->findItem(work.itemBlockType, work.itemId);
        
        // Check validity (2)
        // Catches errors in type or id
        if (work.item == NULL) {
            throw FileException("Corrupted folder header");
        }
        
        items.push_back(work);
    }
}

void FolderEdit::loadContent(FileRead* file) { start_func
    delete cacheFile;
    cacheFile = file;
    cached = 1;
}

int FolderEdit::isContentCached() const { start_func
    return cached;
}

int FolderEdit::markLock() throw_File { start_func
    cacheLoad();
    return ++lockCount;
}

int FolderEdit::markUnlock() { start_func
    --lockCount;
    assert(lockCount >= 0);
    if ((lockCount == 0) && (cacheFile)) {
        // Recache ourselves
        cached = 1;
    }
    return lockCount;
}

int FolderEdit::isLocked() const { start_func
    return lockCount;
}

void FolderEdit::disassociate() throw_File { start_func
    // Ensure not cached- our exception point
    cacheLoad();
    
    // Drop browser node
    dropBrowser();
    
    // Notify objects
    desktop->broadcastObjChange(OBJ_FOLDER | OBJMOD_DELETE, this, 0, 0);
    
    disassociated = 1;
}

void FolderEdit::reassociate(TreeView* node) { start_func
    assert(disassociated);
    
    // Notify objects
    desktop->broadcastObjChange(OBJ_FOLDER | OBJMOD_CREATE, this, 0, 0);
    
    // Browser node
    addToBrowser(node, 0);

    // Ensure written to file
    headerModified = contentModified = 1;
    disassociated = 0;
}

void FolderEdit::setHeaderModified() { start_func
    if (!headerModified) headerModified = 1;
}

void FolderEdit::setContentModified() { start_func
    if (!contentModified) {
        contentModified = 1;
        // No possibility of returning to cache
        delete cacheFile;
        cacheFile = NULL;
    }
}

void FolderEdit::dropBrowser() { start_func
    browserNode = NULL;
}

void FolderEdit::addToBrowser(TreeView* node, int makeCurrent) { start_func
    browserNode = new TreeView(name, this, 0, treeviewEventWrap);
    browserNode->setIcon(icon + 1, icon);
    node->insert(browserNode, makeCurrent);
    // @TODO: Add subitems
}

int FolderEdit::treeviewEvent(int code, int command, int check) { start_func
    if (check) {
        if (command == EDIT_DELETE) return Window::COMMAND_ENABLE;
        return Window::COMMAND_HIDE;
    }

    switch (command) {
        case LV_RCLICK:
            // @TODO: Should actually be a popup with this as an item
            propertiesDialog(desktop->findPreviousFocusWindow());
            return 1;
        
        case EDIT_DELETE:
        case LV_DELETE:
            // Handles any error conditions/cancellation for us
            getWorldEdit()->deleteFolder(this, desktop->findPreviousFocusWindow());
            return 1;
    }
    
    return 0;
}

int FolderEdit::treeviewEventWrap(void* ptr, int code, int command, int check) { start_func
    return ((FolderEdit*)ptr)->treeviewEvent(code, command, check);
}

void FolderEdit::setName(const string& newName, Window* srcWin, Window* exWin) throw_Undo { start_func
    if (name != newName) {
        setHeaderModified();
        if (world) {
            getWorldEdit()->undo.storeUndoName(UndoBuffer::UNDO_FOLDERNAME, id, name, newName, srcWin);
            getWorldEdit()->deindexFolder(this);
        }
        name = newName;
        if (world) getWorldEdit()->indexFolder(this);
        if (browserNode) browserNode->changeName(newName);
    
        desktop->broadcastObjChange(OBJ_FOLDER | OBJMOD_NAME, this, 0, 0, exWin);
    }
}

void FolderEdit::addItem(SaveLoad* item, int index, Window* srcWin, Window* exWin) throw_Undo { start_func
    assert(item);
    
    FolderItem work;

    work.itemBlockType = item->getBlockType();
    work.itemId = item->getId();
    work.item = item;

    assert((work.itemBlockType == WorldFileLoad::BLOCKTYPE_TILESET) ||
           (work.itemBlockType == WorldFileLoad::BLOCKTYPE_SCENE) ||
           (work.itemBlockType == WorldFileLoad::BLOCKTYPE_ANIMGROUP) ||
           (work.itemBlockType == WorldFileLoad::BLOCKTYPE_SCRIPT) ||
           (work.itemBlockType == WorldFileLoad::BLOCKTYPE_FOLDER));
    assert(work.itemId);
    
#ifndef NDEBUG
    // Assert item isn't in list already
    vector<FolderItem>::iterator pos;
    vector<FolderItem>::iterator end = items.end();
    for (pos = items.begin(); pos != end; ++pos) {
        assert(((*pos).itemBlockType != work.itemBlockType) || ((*pos).itemId != work.itemId));
    }
#endif

    // Out of range -or- default = last position (@TODO: sorted)
    if ((index < 0) || (index > (int)items.size()))
        index = items.size();

    setHeaderModified();
    if (world) {
        getWorldEdit()->undo.storeUndoFolderAdd(id, item, index, srcWin);
    }

    // Add to list
    // @TODO: insert at 'index' (right now we assume at end, which is safe... right now.)
    items.push_back(work);
    desktop->broadcastObjChange(OBJ_FOLDER | OBJMOD_CONTENT, this, 0, 0, exWin);

    // @TODO: Update browser node
}

void FolderEdit::removeItem(SaveLoad* item, Window* srcWin, Window* exWin) throw_Undo { start_func
    assert(item);
    
    vector<FolderItem>::iterator pos;
    vector<FolderItem>::iterator end = items.end();
    int index = 0;
    for (pos = items.begin(); pos != end; ++pos, ++index) {
        if ((*pos).item == item) {
            setHeaderModified();
            if (world) {
                getWorldEdit()->undo.storeUndoFolderRemove(id, item, index, srcWin);
            }
            
            items.erase(pos);
            desktop->broadcastObjChange(OBJ_FOLDER | OBJMOD_CONTENT, this, 0, 0, exWin);

            // @TODO: update browser node
            return;
        }
    }
    
    assert(0);
}

int FolderEdit::propertiesDialog(Window* srcWin, Window* exWin) { start_func
    string newName = name;

    if (1) { // @TODO: if (FolderPropertiesDialog::create()->run(&newName, this)) {
        try {
            markLock();
        }
        catch (FileException& e) {
            guiErrorBox(string(e.details), errorTitleFile);
            return 0;
        }
        try {
            getWorldEdit()->undo.preUndoBlock();
            setName(newName, srcWin, exWin);
            getWorldEdit()->undo.postUndoBlock();
        }
        catch (UndoException& e) {
            markUnlock();
            return 0;
        }
        markUnlock();
        return 1;
    }
    
    return 0;
}

int FolderEdit::isHeaderModified() const { start_func
    return headerModified;
}

int FolderEdit::isContentModified() const { start_func
    return contentModified;
}

Uint32 FolderEdit::saveHeader(FileWrite* file) throw_File { start_func
    file->writeStr(name);
    file->writeInt(id);
    file->writeInt(sorted);
    file->writeInt(defaultFont);
    file->writeInt(defaultTileSet);
    file->writeInt(defaultScene);
    file->writeInt(defaultAnimGroup);
    file->writeInt(defaultScript);
    file->writeInt(defaultLibrary);
    file->writeInt(icon);    
    file->writeInt(items.size());
    
    // Write items
    vector<FolderItem>::iterator pos;
    vector<FolderItem>::iterator end = items.end();
    for (pos = items.begin(); pos != end; ++pos) {
        file->writeInt((*pos).itemBlockType);
        file->writeInt((*pos).itemId);
    }
    
    return 1;
}

void FolderEdit::saveContent(FileWrite* file) throw_File { start_func
}

void FolderEdit::saveSuccess() { start_func
    headerModified = 0;
    contentModified = 0;
}

void FolderEdit::cachedContent(FileRead* file, int oldData) { start_func
    // If already cached..
    if (cached) {
        delete cacheFile;
        cacheFile = file;
    }
    else if (oldData) {
        delete file;
    }
    else {
        // Cache ourselves?
        if (!lockCount) {
            delete cacheFile;
            cacheFile = file;
            cached = 1;
        }
        else {
            delete file;
        }
    }
}

